#include "simple_reader.hh"
#include "data_structures.hh"
#include "gac_lang.hh"
#include <cctype>
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <sstream>
#include <string>
#include <stdexcept>

using namespace std;

namespace {
  ostringstream err; //!< for generating error messages to be thrown
}

std::ostream& write_int(int i, std::ostream& out) {
  if(isalpha(i)) {
    out << (char)i;
  } else {
    out << i;
  }
  return out;
}


std::ostream& write_long(long i, std::ostream& out) {
  if((i < 128) && isalpha(i)) {
    out << (char)i;
  } else if(i > 255) {
    for(int j = 24; j >= 0; j -= 8) {
      out << static_cast<char>((i >> j) & 0xFF);
    }
  } else {
    out << i;
  }
  return out;
}


void write_pcode(const pcode_array &pcode, std::ostream &out) {
  short pval;
  char buf[17];

  for(auto it = pcode.begin(); it != pcode.end(); ++it) {
    pval = *it;
    sprintf(buf, " %04hX", pval);
    out << buf;
  }
}

int read_int(std::istream &in) {
  char ch;
  int i;
  in >> ws;
  ch = in.peek();
  if(ch == '-' || !isdigit(ch)) {
    in >> ch;
    i = (int)ch;
  } else {
    in >> i;
  }
  if(!in && !in.eof()) throw runtime_error("Can not read integer");
  if(!in && in.eof()) i = -32768;
  return i;
}

long read_long(std::istream &in) {
  char ch;
  long i = 0;
  in >> ws;
  ch = in.peek();
  if(ch == '-' || !isdigit(ch)) {
    while((in.peek() != EOF) && !isspace(in.peek())) {
      in >> ch;
      i = (i << 8) | (long)ch;
    }
    return i;
  } else {
    in >> i;
  }
  if(!in && !in.eof()) throw runtime_error("Can not read long");
  if(!in && in.eof()) i = -32768;
  return i;
}

std::istream& parse_tab(Adventure &adv, std::istream &in, const string &line) {
  int i, j;
  string tmp(line.substr(1));
  istringstream iss(tmp);

  if(tmp.empty()) return in;
  switch(tmp[0]) {
    case 'e':
      iss >> i >> j;
    break;
  default:
    err << "parsing subcategories for '" << tmp[0] << "' failed";
    throw runtime_error(err.str());
  }
  return in;
}

std::istream& subload_location(Location &loc, std::istream& in, unsigned int &line_num) {
  string tmp;
  int i, j;

  while(in.peek() == '\t') {
    getline(in, tmp);
    line_num++;
    istringstream iss(tmp.substr(1));
    iss >> tmp;
    switch(tmp[0]) {
    case 'e':
      iss >> i >> j;
      loc.exits[i] = j;
      break;
    default:
      err << "parsing location subcategories for '" << tmp[0] << "' failed";
      throw runtime_error(err.str());
    }
  }
  return in;
}

Event& subload_event(Event &evt, std::istream& in, unsigned int &line_num) {
  string tmp;
  char ch;

  while(in.peek() == '\t') {
    getline(in, tmp);
    line_num++;
    istringstream iss(tmp.substr(1));
    iss >> ch;
    switch(ch) {
    case '[':
      if(!getline(iss, tmp, ']')) {
	throw runtime_error("error while reading p-code");
      } else {
	pcode_array arr(pcode_from_string(tmp));
	evt.pcode.push_back(arr) ;
      }
      break;
    case '"':
      if(!getline(iss, tmp, '"')) {
	throw runtime_error("error while reading p-code");
      } else {
	pcode_array arr(compile_gac_lang(tmp));
	evt.pcode.push_back(arr);
      }
      break;
    case '#': //Comment
      continue;
    default:
      err << "parsing event subcategories for '" << ch << "' failed";
      throw runtime_error(err.str());
    }
  }
  return evt;
}

Adventure read_from_file(std::istream& in) {
  Adventure adv;
  string line, tmp;
  int pos, weight;
  unsigned int i, j;
  unsigned int line_num = 0;

  try {
    while(getline(in, line)) {
      line_num++;
      if(line.empty() || line[0] == '#') continue;
      unsigned long selector = 0;
      istringstream iss(line);
      iss >> tmp;
      if(tmp.empty()) continue;
      for(i = 0; i < tmp.size(); ++i) {
	selector = (selector << 8) | ((unsigned long)tmp[i]);
      }
      switch(selector) {
      case 0x68657265: //here
	if(!(iss >> adv.here)) throw invalid_argument("error reading 'here'");
	break;
      case 0x6d76: //mv
	iss >> i >> tmp; //verb number, verb string
	adv.movement_verbs[i] = tmp;
	break;
      case 'v':
	if(iss >> i >> tmp) {
	  adv.verbs[tmp] = i;
	} else {
	  throw runtime_error("error reading verb: " + line);
	}
	break;
      case 'a':
	if(iss >> i >> tmp) {
	  adv.adjectives[tmp] = i;
	} else {
	  throw runtime_error("error reading adjective: " + line);
	}
	break;
      case 'c': // counters
	if(iss >> i >> j) {
	  adv.counters.at(i) = j;
	} else {
	  throw runtime_error("error reading counter: " + line);
	}
	break;
      case 's': //switches
	if(iss >> i >> j) {
	  if(i >= adv.switches.size()) adv.switches.resize(i + 1);
	  adv.switches[i] = j != 0;
	} else {
	  throw runtime_error("error reading switch: " + line);
	}
	break;
      case 'o':
	iss >> i >> pos >> weight >> tmp;
	if(i >= adv.objects.size()) adv.objects.resize(i + 1);
	{
	  Object &obj = adv.objects[i];
	  obj.pos = pos;
	  obj.weight = weight;
	  obj.name = tmp;
	  getline(iss, tmp, '"');
	  getline(iss, obj.desc, '"');
	  if(iss >> i) obj.adjective = i; //else default of -1
	}
	break;
      case 'l':
	iss >> i;
	if(i >= adv.locations.size()) adv.locations.resize(i + 1);
	{
	  Location &loc = adv.locations[i];
	  getline(iss, tmp, '"');
	  getline(iss, loc.desc_short, '"');
	  getline(iss, tmp, '"');
	  getline(iss, loc.desc_long, '"');
	  if(iss >> i) {
	    loc.visited = i == 1;
	  } else {
	    loc.visited = false;
	  }
	  subload_location(loc, in, line_num);
	}
	break;
      case 'm':
	iss >> i;
	if(i >= adv.msgs.size()) adv.msgs.resize(i + 1);
	getline(iss, tmp, '"');
	getline(iss, adv.msgs[i], '"');
	break;
      case 'e':
	{
	  Event evt;
	  iss >> evt.location >> evt.verb >> evt.adverb >> evt.obj1 >> evt.adj1 >> evt.obj2 >> evt.adj2;
	  if(!iss) throw runtime_error("error while reading event");
	  adv.events.push_back(subload_event(evt, in, line_num));
	}
	break;
      case 'E': //high priority events
	getline(iss, tmp);
	if(!tmp.empty()) {
	  pcode_array arr(pcode_from_string(tmp));
	  if(!arr.empty()) adv.hp_events.push_back(arr);
	}
	break;
      case 0x45676163: //"Egac" high priority events
	getline(iss, tmp);
	if(!tmp.empty()) {
	  pcode_array arr(compile_gac_lang(tmp));
	  if(!arr.empty()) adv.hp_events.push_back(arr);
	}
	break;
      case 0x69676e: //"ign" ignored words
	while(iss >> tmp) {
	  adv.ignwords.push_back(tmp);
	}
	break;
      default:
	err << "unknown character '" << tmp[0] << "' in first column";
	throw runtime_error(err.str());
      }
    }
  }
  catch(const std::exception& excp) {
    cerr << "Parsing stopped in line " << line_num << "!\n";
    throw;
  }
  return adv;
}


std::ostream& write_msgs(const Adventure &adv, std::ostream &out) {
  for(unsigned int i = 0; i < adv.msgs.size(); ++i) {
    const string &msg = adv.msgs[i];
    if(msg.empty()) continue;
    out << "m\t" << i << "\t\"" << msg << "\"\n";
  }
  return out;
}

std::ostream& write_to_file(const Adventure &adv, std::ostream &out) {
  out << "here\t" << adv.here << endl;
  out << "# verbs\n";
  for(auto it = adv.verbs.begin(); it != adv.verbs.end(); ++it) {
    out << "v\t" << it->second << '\t' << it->first << '\n';
  }
  out << "# adjectives\n";
  for(auto it = adv.adjectives.begin(); it != adv.adjectives.end(); ++it) {
    out << "a\t" << it->second << '\t' << it->first << '\n';
  }
  out << "# movement verbs\n";
  for(auto it = adv.movement_verbs.begin(); it != adv.movement_verbs.end(); ++it) {
    out << "mv\t" << it->first << '\t' << it->second << '\n';
  }
  out << "# objects\n";
  for(unsigned int i = 0; i < adv.objects.size(); ++i) {
    const Object &obj = adv.objects[i];
    out << "o\t" << i << '\t' << obj.pos << '\t' << obj.weight << '\t' << obj.name << "\t\"" << obj.desc << "\"\t" << obj.adjective << '\n';
  }
  out << "# locations\n";
  for(unsigned int i = 0; i < adv.locations.size(); ++i) {
    const Location &loc = adv.locations[i];
    out << "l\t" << i << "\t\"" << loc.desc_short << "\"\t\"" << loc.desc_long << '"';
    out << '\t' << (loc.visited ? 1 : 0);
    out << endl;
    for(auto it = loc.exits.begin(); it != loc.exits.end(); ++it) {
      out << "\te\t" << it->first << '\t' << it->second << '\n';
    }
  }
  out << "# high priority events\n";
  for(unsigned int i = 0; i < adv.hp_events.size(); ++i) {
    const pcode_array &evt = adv.hp_events[i];
    if(!evt.empty()) {
      out << "E\t";
      write_pcode(evt, out);
      out << '\n';
    }
  }
  out << "# events\n";
  for(unsigned int i = 0; i < adv.events.size(); ++i) {
    const Event &evt = adv.events[i];
    out << "e\t" << evt.location << '\t' << evt.verb << '\t' << evt.adverb << '\t' << evt.obj1 << '\t' << evt.adj1 << '\t' << evt.obj2 << '\t' << evt.adj2 << '\n';
    for_each(evt.pcode.begin(), evt.pcode.end(),
	     [&] (const pcode_array &pcode) {
	       out << "\t[";
	       write_pcode(pcode, out);
	       out << "]\n";
	     }
	     );
  }
  out << "# messages\n";
  write_msgs(adv, out);
  out << "# switches\n";
  for(unsigned int i = 0; i < adv.switches.size(); ++i) {
    if(adv.switches[i]) out << "s\t" << i << '\t' << adv.switches[i] << '\n';
  }
  out << "# counters\n";
  for(unsigned int i = 0; i < adv.counters.size(); ++i) {
    if(adv.counters[i] != 0) {
      out << "c\t" << i << '\t' << adv.counters[i] << '\n';
    }
  }
  out << "# EOF\n";
  return out;
}
